﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using EnvDTE;
using EnvDTE80;
using NielsHebling.UnitTestResultViewerForSL.Core.Data;

namespace NielsHebling.UnitTestResultViewerForSL.Core.Logic
{
    internal class VsShellAbstractionLayer
    {
        //INFO: List of known project type Guids
        //http://www.mztools.com/articles/2008/MZ2008017.aspx
        private const string _silverlightProjectTypeGuid = "{A1591282-1198-4647-A2B1-27E5FF5F6F3B}";

        #region DTE Events
        public BuildEvents BuildEvents { get; set; }
        public SolutionEvents SolutionEvents { get; set; }
        #endregion

        #region Shell
        private EnvDTE.DTE _shell;
        public EnvDTE.DTE Shell
        {
            get 
            {
                if (_shell == null)
                {
                    throw new ArgumentNullException("Shell", "VSShellManager.Shell is null. Could not initialize Shell reference");
                }
                else
                {
                    return _shell;
                }
            }
            set 
            { 
                _shell = value;
            }
        }
        #endregion

        #region OutputWindowPanel
        private EnvDTE.OutputWindowPane _outputWindowPanel;
        public EnvDTE.OutputWindowPane OutputWindowPanel
        {
            get
            {
                if (_outputWindowPanel == null)
                {
                    this.Initialize();
                    if (_outputWindowPanel == null)
                    {
                        throw new ArgumentNullException("OutputWindowPanel",
                                                        "VSShellMananger.OutputWindowPanel is null. Reference not found.");
                    }
                }
                return _outputWindowPanel;
            }
            set { _outputWindowPanel = value; }
        }
        #endregion

        #region Singleton
        private static VsShellAbstractionLayer _instance = null;
        public static VsShellAbstractionLayer Instance
        {
            get 
            {
                if (_instance == null)
                {
                    _instance = new VsShellAbstractionLayer();                    
                }
                return _instance;
            }
        }
        #endregion

        #region ctor
        private VsShellAbstractionLayer()
        {
            
        }
        #endregion

        #region Public methods
        public void Initialize()
        {
            if (Shell != null)
            {
                EnvDTE.Windows windows = Shell.Windows;
                if (windows != null && windows.Count > 0)
                {
                    EnvDTE.Window window = (EnvDTE.Window)windows.Item(EnvDTE.Constants.vsWindowKindOutput);
                    window.Visible = true;

                    EnvDTE.OutputWindow outputWindow = (EnvDTE.OutputWindow)window.Object;
                    OutputWindowPanel = outputWindow.OutputWindowPanes.Add("Silverlight Unit Test Toolbox");
                    OutputWindowPanel.Activate();
                    OutputWindowPanel.OutputString("VsShellAbstractionLayer initialized...\rLoaded into " + Shell.Name + "\rVersion is " + Shell.Version + Environment.NewLine);
                }
            }
        }

        public void Trace(string message)
        {
            if (OutputWindowPanel != null)
            {
                if (!String.IsNullOrEmpty(message))
                {
                    try
                    {
                        if (!message.EndsWith("\r"))
                        {
                            OutputWindowPanel.OutputString(message + "\r");
                            return;
                        }
                        OutputWindowPanel.OutputString(message);
                        return;
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine("Error occured while trying to write some output: " + ex.ToString());                        
                        this.Initialize();
                        return;
                    }
                }
            }
        }        

        internal EnvDTE.OutputWindowPane GetWindowPane(EnvDTE.OutputWindow outputWindow, string item)
        {
            foreach (EnvDTE.OutputWindowPane panel in outputWindow.OutputWindowPanes)
            {
                if (panel.Name == item)
                {
                    return panel;
                }
            }
            return outputWindow.OutputWindowPanes.Add(item);
        }

        public List<ProjectXapItem> GetXapProjectMapping()
        {
            List<ProjectXapItem> result = new List<ProjectXapItem>();

            //Available Statlight version supports multiple xap-files and one result xml
            if (StatLightManager.Instance.StatLightVersion != StatLightVersion.None && StatLightManager.Instance.StatLightVersion == StatLightVersion.v12)
            {
                result.Add(new ProjectXapItem("All Projects", "all"));
            }

            //get all silverlight projects
            foreach (Project project in GetProjects(Shell.Solution.Projects))
            {
                if ((project != null)
                    && (GetProjectTypeGuids(project).IndexOf(_silverlightProjectTypeGuid, StringComparison.OrdinalIgnoreCase) >= 0))
                {
                    string xapFilePath = GetOutputPath(project);
                    result.Add(new ProjectXapItem(project.Name, xapFilePath));
                }
            }

            return result;
        }

        private string GetOutputPath(Project project)
        {
            Properties activeProjectConfigurationProperties = project.ConfigurationManager.ActiveConfiguration.Properties;

            object outputPath = activeProjectConfigurationProperties.Item("OutputPath").Value;
            if (outputPath == null)
            {
                throw new Exception("OutputPath project property not found");
            }
            string xapFileName = project.Properties.Item("SilverlightProject.XapFilename").Value.ToString();
            string outputFilePath = Path.Combine(Path.GetDirectoryName(project.FullName),outputPath.ToString(),xapFileName);

            return outputFilePath;
        }

        //HOWTO: Get the project flavor (subtype) of a Visual Studio project from an add-in
        //http://www.mztools.com/articles/2007/MZ2007016.aspx
        public string GetProjectTypeGuids(EnvDTE.Project proj)
        {
            string projectTypeGuids = "";
            object service = null;
            Microsoft.VisualStudio.Shell.Interop.IVsSolution solution = null;
            Microsoft.VisualStudio.Shell.Interop.IVsHierarchy hierarchy = null;
            Microsoft.VisualStudio.Shell.Interop.IVsAggregatableProject aggregatableProject = null;
            int result = 0;

            service = GetService(proj.DTE, typeof(Microsoft.VisualStudio.Shell.Interop.IVsSolution));
            solution = (Microsoft.VisualStudio.Shell.Interop.IVsSolution)service;

            result = solution.GetProjectOfUniqueName(proj.UniqueName, out hierarchy);

            if (result == 0)
            {
                aggregatableProject = (Microsoft.VisualStudio.Shell.Interop.IVsAggregatableProject)hierarchy;
                result = aggregatableProject.GetAggregateProjectTypeGuids(out projectTypeGuids);
            }

            return projectTypeGuids;
        }

        public object GetService(object serviceProvider, System.Type type)
        {
            return GetService(serviceProvider, type.GUID);
        }

        public object GetService(object serviceProviderObject, System.Guid guid)
        {
            object service = null;
            Microsoft.VisualStudio.OLE.Interop.IServiceProvider serviceProvider = null;
            IntPtr serviceIntPtr;
            int hr = 0;
            Guid SIDGuid;
            Guid IIDGuid;

            SIDGuid = guid;
            IIDGuid = SIDGuid;
            serviceProvider = (Microsoft.VisualStudio.OLE.Interop.IServiceProvider)serviceProviderObject;
            hr = serviceProvider.QueryService(SIDGuid, IIDGuid, out serviceIntPtr);

            if (hr != 0)
            {
                System.Runtime.InteropServices.Marshal.ThrowExceptionForHR(hr);
            }
            else if (!serviceIntPtr.Equals(IntPtr.Zero))
            {
                service = System.Runtime.InteropServices.Marshal.GetObjectForIUnknown(serviceIntPtr);
                System.Runtime.InteropServices.Marshal.Release(serviceIntPtr);
            }

            return service;
        }

        //Thanks to codeplex user nerumo
        private static IEnumerable<Project> GetProjects(IEnumerable parentCollection)
        {
            foreach (Object projectItem in parentCollection)
            {
                if (projectItem is Project)
                {
                    Project project = projectItem as Project;
                    string kind = project.Kind;

                    if (kind == Constants.vsProjectKindSolutionItems)
                    {
                        foreach (var childProjectItem in GetProjects(project.ProjectItems))
                        {
                            yield return childProjectItem;
                        }
                    }
                    else if (kind != Constants.vsProjectKindMisc)
                    {
                        yield return project;
                    }
                }
                else if (projectItem is ProjectItem)
                {
                    var item = projectItem as ProjectItem;
                    if (item.SubProject != null)
                    {
                        string name = item.SubProject.Name;
                        foreach (var project in GetProjects(new[] { item.SubProject }))
                        {
                            yield return project;
                        }
                    }
                }
            }
        }

        internal List<Project> GetFilesFromProjects()
        {
            List<Project> projectsInSolution = new List<Project>();
            Solution currentSolution = GetSolution();
            if (currentSolution != null)
            {
                List<Project> retrievedProjects = currentSolution.Projects.OfType<Project>().ToList();
                if (retrievedProjects != null)
                {
                    foreach (Project project in retrievedProjects)
                    {
                        if (project.Kind != ProjectKinds.vsProjectKindSolutionFolder)
                        {
                            projectsInSolution.Add(project);
                        }
                        else
                        {
                            projectsInSolution.AddRange(HandleSubProjects(project));
                        }
                    }
                }
            }
            return projectsInSolution;
        }

        internal List<Project> HandleSubProjects(Project currentProject)
        {
            List<Project> found = new List<Project>();
            foreach (ProjectItem subItem in currentProject.ProjectItems)
            {
                Project subProject = subItem.SubProject;
                if (subProject != null)
                {
                    if (subProject.Kind == ProjectKinds.vsProjectKindSolutionFolder)
                    {
                        found.AddRange(HandleSubProjects(subProject));
                    }
                    else
                    {
                        found.Add(subProject);
                    }
                }                
            }
            return found;
        }

        public EnvDTE.Solution GetSolution()
        {
            return Shell.Solution;
        }

        public List<EnvDTE.ProjectItem> GetFiles()
        {
            return GetFiles(GetSolution());
        }

        public List<EnvDTE.ProjectItem> GetFiles(EnvDTE.Solution solution)
        {
            var projects = solution.Projects.GetEnumerator();
            List<EnvDTE.ProjectItem> projectItems = new List<EnvDTE.ProjectItem>();
            while (projects.MoveNext())
            {
                var items = ((EnvDTE.Project)projects.Current).ProjectItems.GetEnumerator();
                while (items.MoveNext())
                {
                    var item = (EnvDTE.ProjectItem)items.Current;
                    //Recursion to get all ProjectItems
                    projectItems.AddRange(this.GetFile(item));
                }
            }
            return projectItems;
        }

        public List<EnvDTE.ProjectItem> GetFiles(EnvDTE.Project project)
        {
            var projects = project.ProjectItems.GetEnumerator();
            List<EnvDTE.ProjectItem> projectItems = new List<EnvDTE.ProjectItem>();
            while (projects.MoveNext())
            {
                var item = (EnvDTE.ProjectItem)projects.Current;
                //Recursion to get all ProjectItems
                projectItems.AddRange(this.GetFile(item));
            }
            return projectItems;
        }

        public List<EnvDTE.ProjectItem> GetFile(EnvDTE.ProjectItem item)
        {
            List<EnvDTE.ProjectItem> projectItems = new List<EnvDTE.ProjectItem>();
            
            //base case
            if (item.ProjectItems.Count == 0)
            {
                projectItems.Add(item);
                return projectItems;
            }
            var items = item.ProjectItems.GetEnumerator();
            projectItems.Add(item);
            while (items.MoveNext())
            {                
                var currentItem = (EnvDTE.ProjectItem)items.Current;
                projectItems.AddRange(this.GetFile(currentItem));
            }
            return projectItems;
        }
        
        #endregion

    }
}
